Avastage React Suspense'i andmete hankimiseks peale koodi tükeldamise. Mõistke Hangi-renderdamise-käigus mustrit, veakäsitlust ja tulevikukindlaid lahendusi globaalsetele rakendustele.
React Suspense'i ressursside laadimine: moodsate andmeotsingu mustrite valdamine
Veebiarenduse dünaamilises maailmas on kasutajakogemus (UX) esmatähtis. Rakendustelt oodatakse, et need oleksid kiired, reageerivad ja meeldivad, sõltumata võrgutingimustest või seadme võimekusest. Reacti arendajate jaoks tähendab see sageli keerukat olekuhaldust, kompleksseid laadimisindikaatoreid ja pidevat võitlust andmete hankimise kaskaadidega. Siin tulebki mängu React Suspense, võimas, ehkki sageli valesti mõistetud funktsioon, mis on loodud põhjalikult muutma seda, kuidas me käsitleme asünkroonseid operatsioone, eriti andmete hankimist.
Algselt koodi tĂĽkeldamiseks React.lazy()
abil tutvustatud Suspense'i tõeline potentsiaal peitub selle võimes orkestreerida *mis tahes* asünkroonse ressursi, sealhulgas API-st pärinevate andmete laadimist. See põhjalik juhend süveneb React Suspense'i ressursside laadimisse, uurides selle põhikontseptsioone, fundamentaalseid andmete hankimise mustreid ja praktilisi kaalutlusi jõudluspõhiste ja vastupidavate globaalsete rakenduste ehitamisel.
Andmete hankimise areng Reactis: imperatiivsest deklaratiivseks
Aastaid tugines andmete hankimine Reacti komponentides peamiselt levinud mustrile: useEffect
hook'i kasutamine API-kutse algatamiseks, laadimis- ja veaolukordade haldamine useState
'iga ning tingimuslik renderdamine nende olekute põhjal. Kuigi see lähenemine on funktsionaalne, tõi see sageli kaasa mitmeid väljakutseid:
- Laadimisolekute vohamine: Peaaegu iga andmeid vajav komponent vajas oma
isLoading
,isError
jadata
olekuid, mis viis korduva koodi kirjutamiseni. - Kaskaadid ja võidujooksu tingimused: Pessa paigutatud komponentide andmete hankimine tulemuseks oli sageli järjestikused päringud (kaskaadid), kus vanemkomponent hangib andmed, seejärel renderdab, siis laps-komponent hangib oma andmed jne. See pikendas üldist laadimisaega. Võidujooksu tingimused võisid tekkida ka siis, kui algatati mitu päringut ja vastused saabusid vales järjekorras.
- Keeruline veakäsitlus: Veateadete ja taastamisloogika jaotamine arvukate komponentide vahel võis olla tülikas, nõudes prop'ide edasiandmist (prop drilling) või globaalseid olekuhalduslahendusi.
- Ebameeldiv kasutajakogemus: Mitmete spinnerite ilmumine ja kadumine või äkilised sisu nihked (layout shifts) võisid luua kasutajatele häiriva kogemuse.
- Andmete ja olekute edastamine prop'ide kaudu: Hangitud andmete ja seotud laadimis-/veaolekute edastamine läbi mitme komponenditaseme muutus tavaliseks keerukuse allikaks.
Vaatleme tĂĽĂĽpilist andmete hankimise stsenaariumi ilma Suspense'ita:
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setIsLoading(true);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP viga! staatus: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
setError(e);
} finally {
setIsLoading(false);
}
};
fetchUser();
}, [userId]);
if (isLoading) {
return <p>Kasutajaprofiili laadimine...</p>;
}
if (error) {
return <p style=\"color: red;\">Viga: {error.message}</p>;
}
if (!user) {
return <p>Kasutaja andmed pole saadaval.</p>;
}
return (
<div>
<h2>Kasutaja: {user.name}</h2>
<p>E-post: {user.email}</p>
<!-- Rohkem kasutaja andmeid -->
</div>
);
}
function App() {
return (
<div>
<h1>Tere tulemast rakendusse</h1>
<UserProfile userId={\"123\"} />
</div>
);
}
See muster on laialt levinud, kuid see sunnib komponenti haldama oma asĂĽnkroonset olekut, mis viib sageli tihedalt seotud suhteni kasutajaliidese ja andmete hankimise loogika vahel. Suspense pakub deklaratiivsemat ja sujuvamat alternatiivi.
React Suspense'i mõistmine väljaspool koodi tükeldamist
Enamik arendajaid puutub Suspense'iga esmakordselt kokku React.lazy()
kaudu koodi tükeldamiseks, kus see võimaldab edasi lükata komponendi koodi laadimist, kuni seda vaja läheb. Näiteks:
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./MyHeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Komponendi laadimine...</div>}>
<LazyComponent />
</Suspense>
);
}
Selles stsenaariumis, kui MyHeavyComponent
pole veel laaditud, pĂĽĂĽab <Suspense>
piir kinni lazy()
poolt visatud promise'i ja kuvab fallback
'i, kuni komponendi kood on valmis. Siin on peamine arusaam, et Suspense töötab renderdamise ajal visatud promise'ide püüdmisega.
See mehhanism ei ole ainuomane koodi laadimisele. Iga renderdamise ajal kutsutud funktsioon, mis viskab promise'i (nt kuna ressurss pole veel saadaval), saab kinni püüda kõrgemal komponendipuus asuva Suspense'i piiriga. Kui promise laheneb, proovib React komponenti uuesti renderdada ja kui ressurss on nüüd saadaval, peidetakse varu-UI ja kuvatakse tegelik sisu.
Suspense'i põhimõisted andmete hankimiseks
Selleks, et Suspense'i andmete hankimiseks ära kasutada, peame mõistma mõnda põhiprintsiipi:
1. Promise'i viskamine
Erinevalt traditsioonilisest asĂĽnkroonsest koodist, mis kasutab promise'ide lahendamiseks async/await
, tugineb Suspense funktsioonile, mis *viskab* promise'i, kui andmed pole valmis. Kui React proovib renderdada komponenti, mis kutsub sellist funktsiooni, ja andmed on veel ootel, visatakse promise. Seejärel React 'peatab' selle komponendi ja selle laste renderdamise, otsides lähimat <Suspense>
piiri.
2. Suspense'i piir
Komponent <Suspense>
toimib promise'ide veapiirina. See võtab vastu fallback
prop'i, mis on kasutajaliides, mida renderdada, kui mõni selle lastest (või nende järeltulijatest) on peatatud olekus (st viskab promise'i). Kui kõik selle alampuus visatud promise'id lahenevad, asendatakse varu-UI tegeliku sisuga.
Üks Suspense'i piir võib hallata mitut asünkroonset operatsiooni. Näiteks, kui teil on samas <Suspense>
piiris kaks komponenti ja kumbki peab andmeid hankima, kuvatakse varu-UI, kuni *mõlemad* andmete hankimised on lõpule viidud. See väldib osalise kasutajaliidese näitamist ja pakub koordineeritumat laadimiskogemust.
3. Vahemälu/ressursihaldur (kasutajapoolne vastutus)
Oluline on, et Suspense ise ei tegele andmete hankimise ega vahemällu salvestamisega. See on pelgalt koordineerimismehhanism. Et Suspense andmete hankimiseks tööle panna, on vaja kihti, mis:
- Algatab andmete hankimise.
- Salvestab tulemuse (lahendatud andmed või ootel promise) vahemällu.
- Pakub sĂĽnkroonset
read()
meetodit, mis kas tagastab vahemällu salvestatud andmed kohe (kui need on saadaval) või viskab ootel oleva promise'i (kui mitte).
See 'ressursihaldur' on tavaliselt implementeeritud lihtsa vahemälu abil (nt Map või objekt), et salvestada iga ressursi olekut (ootel, lahendatud või veaga). Kuigi saate seda demonstratsiooni eesmärgil käsitsi ehitada, kasutaksite reaalses rakenduses robustset andmete hankimise teeki, mis integreerub Suspense'iga.
4. Samaaegne režiim (React 18 täiustused)
Kuigi Suspense'i saab kasutada ka vanemates Reacti versioonides, vallandub selle täielik võimsus samaaegse Reactiga (Concurrent React) (lubatud vaikimisi React 18-s createRoot
'iga). Samaaegne režiim võimaldab Reactil renderdamistööd katkestada, peatada ja jätkata. See tähendab:
- Mitteblokeerivad kasutajaliidese uuendused: Kui Suspense näitab varu-UI-d, saab React jätkata teiste kasutajaliidese osade renderdamist, mis ei ole peatatud, või isegi valmistada uut kasutajaliidest taustal ette ilma põhilõime blokeerimata.
- Ăśleminekud (Transitions): Uued API-d nagu
useTransition
võimaldavad märkida teatud uuendused 'üleminekuteks', mida React saab katkestada ja muuta vähem kiireloomuliseks, pakkudes sujuvamaid kasutajaliidese muudatusi andmete hankimise ajal.
Andmete hankimise mustrid Suspense'iga
Uurime andmete hankimise mustrite arengut Suspense'i tulekuga.
1. muster: Hangi-siis-renderda (traditsiooniline Suspense'i mähisega)
See on klassikaline lähenemine, kus andmed hangitakse ja alles seejärel renderdatakse komponent. Kuigi see ei kasuta andmete jaoks otse 'viska promise' mehhanismi, saate mässida komponendi, mis *lõpuks* renderdab andmeid, Suspense'i piiri sisse, et pakkuda varu-UI-d. See on rohkem Suspense'i kasutamine üldise laadimis-UI orkestreerijana komponentide jaoks, mis lõpuks valmis saavad, isegi kui nende sisemine andmete hankimine on endiselt traditsiooniline useEffect
-põhine.
import React, { Suspense, useState, useEffect } from 'react';
function UserDetails({ userId }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchUserData = async () => {
setIsLoading(true);
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
setUser(data);
setIsLoading(false);
};
fetchUserData();
}, [userId]);
if (isLoading) {
return <p>Kasutaja andmete laadimine...</p>;
}
return (
<div>
<h3>Kasutaja: {user.name}</h3>
<p>E-post: {user.email}</p>
</div>
);
}
function App() {
return (
<div>
<h1>Hangi-siis-renderda näide</h1>
<Suspense fallback={<div>Lehe ĂĽldine laadimine...</div>}>
<UserDetails userId={\"1\"} />
</Suspense>
</div>
);
}
Plussid: Lihtne mõista, tagasiühilduv. Saab kasutada kiire viisina globaalse laadimisoleku lisamiseks.
Miinused: Ei kõrvalda korduvat koodi UserDetails
sees. On endiselt vastuvõtlik kaskaadidele, kui komponendid hangivad andmeid järjestikku. Ei kasuta tegelikult Suspense'i 'viska-ja-püüa' mehhanismi andmete endi jaoks.
2. muster: Renderda-siis-hangi (hankimine renderdamise sees, mitte toodanguks)
See muster on peamiselt illustreerimiseks, mida mitte teha otse Suspense'iga, kuna see võib viia lõputute tsükliteni või jõudlusprobleemideni, kui seda hoolikalt ei hallata. See hõlmab katset hankida andmeid või kutsuda peatavat funktsiooni otse komponendi renderdamisfaasis, *ilma* korraliku vahemälumehhanismita.
// ÄRA KASUTA SEDA TOODANGUS ILMA KORRALIKU VAHMÄLUKIHIta
// See on puhtalt illustreerimaks, kuidas otsene 'viskamine' kontseptuaalselt toimida võiks.
let fetchedData = null;
let dataPromise = null;
function fetchDataSynchronously(url) {
if (fetchedData) {
return fetchedData;
}
if (!dataPromise) {
dataPromise = fetch(url)
.then(res => res.json())
.then(data => { fetchedData = data; dataPromise = null; return data; })
.catch(err => { dataPromise = null; throw err; });
}
throw dataPromise; // Siin rakendub Suspense
}
function UserDetailsBadExample({ userId }) {
const user = fetchDataSynchronously(`/api/users/${userId}`);
return (
<div>
<h3>Kasutaja: {user.name}</h3>
<p>E-post: {user.email}</p>
</div>
);
}
function App() {
return (
<div>
<h1>Renderda-siis-hangi (illustratiivne, OTSE MITTE SOOVITATAV)</h1>
<Suspense fallback={<div>Kasutaja laadimine...</div>}>
<UserDetailsBadExample userId={\"2\"} />
</Suspense>
</div>
);
}
Plussid: Näitab, kuidas komponent saab otse andmeid 'küsida' ja peatuda, kui need pole valmis.
Miinused: Väga problemaatiline toodanguks. See käsitsi, globaalne fetchedData
ja dataPromise
süsteem on lihtsustatud, ei käsitle mitut päringut, kehtetuks tunnistamist ega veaolekuid robustselt. See on 'viska-promise' kontseptsiooni primitiivne illustratsioon, mitte muster, mida kasutusele võtta.
3. muster: Hangi-renderdamise-käigus (ideaalne Suspense'i muster)
See on paradigmanihe, mida Suspense andmete hankimiseks tõeliselt võimaldab. Selle asemel, et oodata komponendi renderdamist enne andmete hankimist või hankida kõik andmed ette, tähendab Hangi-renderdamise-käigus seda, et hakkate andmeid hankima *niipea kui võimalik*, sageli *enne* või *samaaegselt* renderdamisprotsessiga. Komponendid seejärel 'loevad' andmeid vahemälust ja kui andmed pole valmis, peatuvad nad. Põhiidee on eraldada andmete hankimise loogika komponendi renderdamisloogikast.
Hangi-renderdamise-käigus mustri implementeerimiseks on vaja mehhanismi, et:
- Algatada andmete hankimine väljaspool komponendi renderdamisfunktsiooni (nt marsruudile sisenemisel või nupule klõpsamisel).
- Salvestada promise või lahendatud andmed vahemällu.
- Pakkuda komponentidele viisi sellest vahemälust 'lugemiseks'. Kui andmed pole veel saadaval, viskab lugemisfunktsioon ootel oleva promise'i.
See muster lahendab kaskaadiprobleemi. Kui kaks erinevat komponenti vajavad andmeid, saab nende päringud algatada paralleelselt ja kasutajaliides ilmub alles siis, kui *mõlemad* on valmis, mida orkestreerib üks Suspense'i piir.
Käsitsi implementeerimine (mõistmiseks)
Et mõista aluseks olevaid mehaanikaid, loome lihtsustatud käsitsi ressursihalduri. Reaalses rakenduses kasutaksite spetsiaalset teeki.
import React, { Suspense } from 'react';
// --- Lihtne vahemälu/ressursihaldur --- //
const cache = new Map();
function createResource(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
},
};
}
function fetchData(key, fetcher) {
if (!cache.has(key)) {
cache.set(key, createResource(fetcher()));
}
return cache.get(key);
}
// --- Andmete hankimise funktsioonid --- //
const fetchUserById = (id) => {
console.log(`Kasutaja ${id} hankimine...`);
return new Promise(resolve => setTimeout(() => {
const users = {
'1': { id: '1', name: 'Alice Smith', email: 'alice@example.com' },
'2': { id: '2', name: 'Bob Johnson', email: 'bob@example.com' },
'3': { id: '3', name: 'Charlie Brown', email: 'charlie@example.com' }
};
resolve(users[id]);
}, 1500));
};
const fetchPostsByUserId = (userId) => {
console.log(`Kasutaja ${userId} postituste hankimine...`);
return new Promise(resolve => setTimeout(() => {
const posts = {
'1': [{ id: 'p1', title: 'Minu esimene postitus' }, { id: 'p2', title: 'Reisiseiklused' }],
'2': [{ id: 'p3', title: 'Programmeerimise taipamised' }],
'3': [{ id: 'p4', title: 'Globaalsed trendid' }, { id: 'p5', title: 'Kohalik köök' }]
};
resolve(posts[userId] || []);
}, 2000));
};
// --- Komponendid --- //
function UserProfile({ userId }) {
const userResource = fetchData(`user-${userId}`, () => fetchUserById(userId));
const user = userResource.read(); // See peatab renderdamise (suspend), kui kasutaja andmed pole valmis
return (
<div>
<h3>Kasutaja: {user.name}</h3>
<p>E-post: {user.email}</p>
</div>
);
}
function UserPosts({ userId }) {
const postsResource = fetchData(`posts-${userId}`, () => fetchPostsByUserId(userId));
const posts = postsResource.read(); // See peatab renderdamise (suspend), kui postituste andmed pole valmis
return (
<div>
<h4>Kasutaja {userId} postitused:</h4>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
{posts.length === 0 && <li>Postitusi ei leitud.</li>}
</ul>
</div>
);
}
// --- Rakendus --- //
let initialUserResource = null;
let initialPostsResource = null;
function prefetchDataForUser(userId) {
initialUserResource = fetchData(`user-${userId}`, () => fetchUserById(userId));
initialPostsResource = fetchData(`posts-${userId}`, () => fetchPostsByUserId(userId));
}
// Eellae mõned andmed enne, kui App komponent üldse renderdatakse
prefetchDataForUser('1');
function App() {
return (
<div>
<h1>Hangi-renderdamise-käigus Suspense'iga</h1>
<p>See demonstreerib, kuidas andmete hankimine võib toimuda paralleelselt, koordineerituna Suspense'i poolt.</p>
<Suspense fallback={<div>Kasutajaprofiili ja postituste laadimine...</div>}>
<UserProfile userId={\"1\"} />
<UserPosts userId={\"1\"} />
</Suspense>
<h2>Teine jaotis</h2>
<Suspense fallback={<div>Teise kasutaja laadimine...</div>}>
<UserProfile userId={\"2\"} />
</Suspense>
</div>
);
}
Selles näites:
- Funktsioonid
createResource
jafetchData
seadistavad põhilise vahemälumehhanismi. - Kui
UserProfile
võiUserPosts
kutsuvadresource.read()
, saavad nad kas andmed kohe või visatakse promise. - Lähim
<Suspense>
piir pĂĽĂĽab promise('id) kinni ja kuvab oma varu-UI. - Oluline on, et saame kutsuda
prefetchDataForUser('1')
*enne* kuiApp
komponent renderdatakse, mis võimaldab andmete hankimist alustada veelgi varem.
Teegid Hangi-renderdamise-käigus mustri jaoks
Robustse ressursihalduri käsitsi ehitamine ja hooldamine on keeruline. Õnneks on mitmed küpsed andmete hankimise teegid Suspense'i kasutusele võtnud või võtmas, pakkudes lahingus testitud lahendusi:
- React Query (TanStack Query): Pakub võimsat andmete hankimise ja vahemällu salvestamise kihti Suspense'i toega. See pakub hook'e nagu
useQuery
, mis võivad peatuda. See sobib suurepäraselt REST API-de jaoks. - SWR (Stale-While-Revalidate): Teine populaarne ja kergekaaluline andmete hankimise teek, mis toetab täielikult Suspense'i. Ideaalne REST API-de jaoks, see keskendub andmete kiirele pakkumisele (aegunud) ja seejärel taustal nende uuesti valideerimisele.
- Apollo Client: Põhjalik GraphQL klient, millel on tugev Suspense'i integratsioon GraphQL päringute ja mutatsioonide jaoks.
- Relay: Facebooki enda GraphQL klient, mis on algusest peale loodud Suspense'i ja samaaegse Reacti jaoks. See nõuab spetsiifilist GraphQL skeemi ja kompileerimisetappi, kuid pakub võrratut jõudlust ja andmete järjepidevust.
- Urql: Kergekaaluline ja väga kohandatav GraphQL klient Suspense'i toega.
Need teegid abstraheerivad ressursside loomise ja haldamise keerukused, tegeledes vahemällu salvestamise, uuesti valideerimise, optimistlike uuenduste ja veakäsitlusega, muutes Hangi-renderdamise-käigus mustri implementeerimise palju lihtsamaks.
4. muster: Eellaadimine Suspense'i toetavate teekidega
Eellaadimine on võimas optimeerimine, kus te hangite ennetavalt andmeid, mida kasutaja tõenäoliselt lähitulevikus vajab, enne kui ta neid isegi selgesõnaliselt taotleb. See võib tajutavat jõudlust drastiliselt parandada.
Suspense'i toetavate teekidega muutub eellaadimine sujuvaks. Saate käivitada andmete hankimisi kasutaja interaktsioonidel, mis ei muuda kohe kasutajaliidest, näiteks lingi kohal hõljumine või nupule hiirega liikumine.
import React, { Suspense } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
// Oletame, et need on sinu API-kutsed
const fetchProductById = async (id) => {
console.log(`Toote ${id} hankimine...`);
return new Promise(resolve => setTimeout(() => {
const products = {
'A001': { id: 'A001', name: 'Globaalne Vidin X', price: 29.99, description: 'MitmekĂĽlgne vidin rahvusvaheliseks kasutamiseks.' },
'B002': { id: 'B002', name: 'Universaalne Seade Y', price: 149.99, description: 'Tipptasemel seade, armastatud ĂĽle maailma.' },
};
resolve(products[id]);
}, 1000));
};
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true, // Luba Suspense vaikimisi kõikidele päringutele
},
},
});
function ProductDetails({ productId }) {
const { data: product } = useQuery({
queryKey: ['product', productId],
queryFn: () => fetchProductById(productId),
});
return (
<div style={{\"border\": \"1px solid #ccc\", \"padding\": \"15px\", \"margin\": \"10px 0\"}}>
<h3>{product.name}</h3>
<p>Hind: ${product.price.toFixed(2)}</p>
<p>{product.description}</p>
</div>
);
}
function ProductList() {
const handleProductHover = (productId) => {
// Eellae andmed, kui kasutaja liigub hiirega toote lingi kohale
queryClient.prefetchQuery({
queryKey: ['product', productId],
queryFn: () => fetchProductById(productId),
});
console.log(`Toote ${productId} eellaadimine`);
};
return (
<div>
<h2>Saadaval tooted:</h2>
<ul>
<li>
<a href=\"#\" onMouseEnter={() => handleProductHover('A001')}
onClick={(e) => { e.preventDefault(); /* Navigeeri või näita detaile */ }}
>Globaalne Vidin X (A001)</a>
</li>
<li>
<a href=\"#\" onMouseEnter={() => handleProductHover('B002')}
onClick={(e) => { e.preventDefault(); /* Navigeeri või näita detaile */ }}
>Universaalne Seade Y (B002)</a>
</li>
</ul>
<p>Hõljutage kursorit toote lingi kohal, et näha eellaadimist tegevuses. Jälgimiseks avage võrgustiku vahekaart.</p>
</div>
);
}
function App() {
const [showProductA, setShowProductA] = React.useState(false);
const [showProductB, setShowProductB] = React.useState(false);
return (
<QueryClientProvider client={queryClient}>
<h1>Eellaadimine React Suspense'iga (React Query)</h1>
<ProductList />
<button onClick={() => setShowProductA(true)}>Näita Globaalset Vidina X</button>
<button onClick={() => setShowProductB(true)}>Näita Universaalset Seadet Y</button>
{showProductA && (
<Suspense fallback={<p>Globaalse Vidina X laadimine...</p>}>
<ProductDetails productId=\"A001\" />
</Suspense>
)}
{showProductB && (
<Suspense fallback={<p>Universaalse Seadme Y laadimine...</p>}>
<ProductDetails productId=\"B002\" />
</Suspense>
)}
</QueryClientProvider>
);
}
Selles näites käivitab toote lingi kohal hõljumine `queryClient.prefetchQuery`, mis algatab andmete hankimise taustal. Kui kasutaja seejärel klõpsab nupule toote üksikasjade kuvamiseks ja andmed on juba eellaadimise tulemusena vahemälus, renderdatakse komponent koheselt ilma peatumata. Kui eellaadimine on veel pooleli või seda ei algatatud, kuvab Suspense varu-UI, kuni andmed on valmis.
Veakäsitlus Suspense'i ja veapiiridega
Kuigi Suspense käsitleb 'laadimise' olekut varu-UI kuvamisega, ei käsitle see otse 'vea' olekuid. Kui peatava komponendi poolt visatud promise lükatakse tagasi (st andmete hankimine ebaõnnestub), levib see viga komponendipuus ülespoole. Nende vigade graatsiliseks käsitlemiseks ja sobiva kasutajaliidese kuvamiseks peate kasutama veapiire (Error Boundaries).
Veapiir on Reacti komponent, mis implementeerib kas componentDidCatch
või static getDerivedStateFromError
elutsükli meetodeid. See püüab kinni JavaScripti vead kõikjal oma laste komponendipuus, sealhulgas vigadest, mille on visanud promise'id, mida Suspense tavaliselt püüaks, kui need oleksid ootel.
import React, { Suspense, useState } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
// --- Veapiiri komponent --- //
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// Uuenda olekut, et järgmine renderdamine näitaks varu-UI-d.
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Võid vea logida ka vearaportiteenusesse
console.error(\"PĂĽĂĽti kinni viga:\", error, errorInfo);
}
render() {
if (this.state.hasError) {
// Võid renderdada mis tahes kohandatud varu-UI
return (
<div style={{\"border\": \"2px solid red\", \"padding\": \"20px\", \"margin\": \"20px 0\", \"background\": \"#ffe0e0\"}}>
<h2>Midagi läks valesti!</h2>
<p>{this.state.error && this.state.error.message}</p>
<p>Palun proovige lehte värskendada või võtke ühendust toega.</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>Proovi uuesti</button>
</div>
);
}
return this.props.children;
}
}
// --- Andmete hankimine (võimaliku veaga) --- //
const fetchItemById = async (id) => {
console.log(`Proovin hankida eset ${id}...`);
return new Promise((resolve, reject) => setTimeout(() => {
if (id === 'error-item') {
reject(new Error('Eseme laadimine ebaõnnestus: Võrk pole kättesaadav või eset ei leitud.'));
} else if (id === 'slow-item') {
resolve({ id: 'slow-item', name: 'Aeglaselt kohale toimetatud', data: 'See ese võttis aega, kuid saabus!', status: 'success' });
} else {
resolve({ id, name: `Ese ${id}`, data: `Andmed eseme ${id} kohta` });
}
}, id === 'slow-item' ? 3000 : 800));
};
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true,
retry: false, // Demonstratsiooniks lülita kordusproovimine välja, et viga oleks kohene
},
},
});
function DisplayItem({ itemId }) {
const { data: item } = useQuery({
queryKey: ['item', itemId],
queryFn: () => fetchItemById(itemId),
});
return (
<div>
<h3>Eseme ĂĽksikasjad:</h3>
<p>ID: {item.id}</p>
<p>Nimi: {item.name}</p>
<p>Andmed: {item.data}</p>
</div>
);
}
function App() {
const [fetchType, setFetchType] = useState('normal-item');
return (
<QueryClientProvider client={queryClient}>
<h1>Suspense ja veapiirid</h1>
<div>
<button onClick={() => setFetchType('normal-item')}>Hangi tavaline ese</button>
<button onClick={() => setFetchType('slow-item')}>Hangi aeglane ese</button>
<button onClick={() => setFetchType('error-item')}>Hangi vigane ese</button>
</div>
<MyErrorBoundary>
<Suspense fallback={<p>Eseme laadimine Suspense'i kaudu...</p>}>
<DisplayItem itemId={fetchType} />
</Suspense>
</MyErrorBoundary>
</QueryClientProvider>
);
}
Mässides oma Suspense'i piiri (või komponente, mis võivad peatuda) veapiiriga, tagate, et võrgutõrked või serverivead andmete hankimisel püütakse kinni ja käsitletakse graatsiliselt, vältides kogu rakenduse kokkujooksmist. See pakub robustset ja kasutajasõbralikku kogemust, võimaldades kasutajatel probleemist aru saada ja potentsiaalselt uuesti proovida.
Oleku haldamine ja andmete kehtetuks tunnistamine Suspense'iga
On oluline selgitada, et React Suspense tegeleb peamiselt asünkroonsete ressursside esialgse laadimise olekuga. See ei halda iseenesest kliendipoolset vahemälu, ei tegele andmete kehtetuks tunnistamisega ega orkestreeri mutatsioone (loomine, uuendamine, kustutamine) ja nendele järgnevaid kasutajaliidese uuendusi.
Siin muutuvad Suspense'i toetavad andmete hankimise teegid (React Query, SWR, Apollo Client, Relay) asendamatuks. Nad täiendavad Suspense'i, pakkudes:
- Robustne vahemällu salvestamine: Nad hoiavad keerukat mälusisest vahemälu hangitud andmetest, serveerides neid koheselt, kui need on saadaval, ja tegeledes taustal uuesti valideerimisega.
- Andmete kehtetuks tunnistamine ja uuesti hankimine: Nad pakuvad mehhanisme vahemällu salvestatud andmete 'aegunuks' märkimiseks ja nende uuesti hankimiseks (nt pärast mutatsiooni, kasutaja interaktsiooni või akna fookusesse sattumist).
- Optimistlikud uuendused: Mutatsioonide puhul võimaldavad nad teil kasutajaliidest koheselt (optimistlikult) uuendada API-kutse oodatava tulemuse põhjal ja seejärel tagasi pöörata, kui tegelik API-kutse ebaõnnestub.
- Globaalne oleku sünkroniseerimine: Nad tagavad, et kui andmed muutuvad ühes teie rakenduse osas, uuendatakse automaatselt kõik komponendid, mis neid andmeid kuvavad.
- Laadimis- ja veaolekud mutatsioonide jaoks: Kuigi
useQuery
võib peatuda, pakubuseMutation
tavaliseltisLoading
jaisError
olekuid mutatsiooniprotsessi enda jaoks, kuna mutatsioonid on sageli interaktiivsed ja nõuavad kohest tagasisidet.
Ilma robustse andmete hankimise teegita oleks nende funktsioonide implementeerimine käsitsi loodud Suspense'i ressursihalduri peale märkimisväärne ettevõtmine, mis nõuaks sisuliselt oma andmete hankimise raamistiku ehitamist.
Praktilised kaalutlused ja parimad praktikad
Suspense'i kasutuselevõtt andmete hankimiseks on oluline arhitektuuriline otsus. Siin on mõned praktilised kaalutlused globaalse rakenduse jaoks:
1. Kõik andmed ei vaja Suspense'i
Suspense on ideaalne kriitiliste andmete jaoks, mis mõjutavad otseselt komponendi esialgset renderdamist. Mittekriitiliste andmete, taustal toimuvate hankimiste või andmete jaoks, mida saab laisalt laadida ilma tugeva visuaalse mõjuta, võib traditsiooniline useEffect
või eelrenderdamine endiselt sobida. Suspense'i liigne kasutamine võib viia vähem granulaarse laadimiskogemuseni, kuna üks Suspense'i piir ootab *kõigi* oma laste lahenemist.
2. Suspense'i piiride granulaarsus
Paigutage oma <Suspense>
piirid läbimõeldult. Üks suur piir rakenduse tipus võib peita terve lehe spinneri taha, mis võib olla masendav. Väiksemad, granulaarsemad piirid võimaldavad teie lehe erinevatel osadel iseseisvalt laadida, pakkudes progressiivsemat ja reageerivamat kogemust. Näiteks piir ümber kasutajaprofiili komponendi ja teine soovitatud toodete loendi ümber.
<div>
<h1>Tooteleht</h1>
<Suspense fallback={<p>Põhitoodete üksikasjade laadimine...</p>}>
<ProductDetails id=\"prod123\" />
</Suspense>
<hr />
<h2>Seotud tooted</h2>
<Suspense fallback={<p>Seotud toodete laadimine...</p>}>
<RelatedProducts category=\"electronics\" />
</Suspense>
</div>
See lähenemine tähendab, et kasutajad näevad põhitoodete üksikasju isegi siis, kui seotud tooted alles laadivad.
3. Serveripoolne renderdamine (SSR) ja voogedastusega HTML
React 18 uued voogedastusega SSR API-d (renderToPipeableStream
) integreeruvad täielikult Suspense'iga. See võimaldab teie serveril saata HTML-i kohe, kui see on valmis, isegi kui lehe osad (nagu andmetest sõltuvad komponendid) alles laadivad. Server saab voogedastada kohatäitja (Suspense'i varu-UI-st) ja seejärel voogedastada tegeliku sisu, kui andmed lahenevad, ilma et oleks vaja täielikku kliendipoolset uuesti renderdamist. See parandab oluliselt tajutavat laadimisjõudlust globaalsetele kasutajatele erinevates võrgutingimustes.
4. Järkjärguline kasutuselevõtt
Te ei pea kogu oma rakendust ümber kirjutama, et Suspense'i kasutada. Saate seda kasutusele võtta järk-järgult, alustades uutest funktsioonidest või komponentidest, mis saaksid selle deklaratiivsetest laadimismustritest kõige rohkem kasu.
5. Tööriistad ja silumine
Kuigi Suspense lihtsustab komponendi loogikat, võib silumine olla erinev. React DevTools pakub ülevaadet Suspense'i piiridest ja nende olekutest. Tutvuge sellega, kuidas teie valitud andmete hankimise teek oma sisemist olekut paljastab (nt React Query Devtools).
6. Suspense'i varu-UI-de ajalõpud
Väga pikkade laadimisaegade puhul võiksite oma Suspense'i varu-UI-le lisada ajalõpu või lülituda pärast teatud viivitust üksikasjalikuma laadimisindikaatori juurde. React 18 hook'id useDeferredValue
ja useTransition
aitavad hallata neid nüansseeritumaid laadimisolekuid, võimaldades teil näidata kasutajaliidese 'vana' versiooni, samal ajal kui uusi andmeid hangitakse, või lükata edasi mittekiireloomulisi uuendusi.
Andmete hankimise tulevik Reactis: React Server Components ja edasi
Andmete hankimise teekond Reactis ei lõpe kliendipoolse Suspense'iga. React Server Components (RSC) esindab olulist arengut, lubades hägustada piire kliendi ja serveri vahel ning optimeerida andmete hankimist veelgi.
- React Server Components (RSC): Need komponendid renderdatakse serveris, hangivad oma andmed otse ja saadavad seejärel brauserisse ainult vajaliku HTML-i ja kliendipoolse JavaScripti. See kõrvaldab kliendipoolsed kaskaadid, vähendab paketi suurust ja parandab esialgset laadimisjõudlust. RSC-d töötavad käsikäes Suspense'iga: serverikomponendid võivad peatuda, kui nende andmed pole valmis, ja server saab kliendile voogedastada Suspense'i varu-UI, mis asendatakse seejärel, kui andmed lahenevad. See on mängumuutja keerukate andmenõuetega rakenduste jaoks, pakkudes sujuvat ja ülijõudsat kogemust, mis on eriti kasulik kasutajatele erinevates geograafilistes piirkondades erineva latentsusajaga.
- Ühtne andmete hankimine: Reacti pikaajaline visioon hõlmab ühtset lähenemist andmete hankimisele, kus põhi-raamistik või tihedalt integreeritud lahendused pakuvad esmaklassilist tuge andmete laadimiseks nii serveris kui ka kliendis, mida kõike orkestreerib Suspense.
- Teekide jätkuv areng: Andmete hankimise teegid arenevad edasi, pakkudes veelgi keerukamaid funktsioone vahemällu salvestamiseks, kehtetuks tunnistamiseks ja reaalajas uuendusteks, tuginedes Suspense'i põhivõimekustele.
Kuna React jätkab küpsemist, saab Suspense'ist üha kesksem osa puslest, et ehitada ülijõudsaid, kasutajasõbralikke ja hooldatavaid rakendusi. See suunab arendajaid deklaratiivsema ja vastupidavama viisi poole asünkroonsete operatsioonide käsitlemisel, viies keerukuse üksikutelt komponentidelt hästi hallatud andmekihti.
Kokkuvõte
React Suspense, algselt koodi tükeldamise funktsioon, on arenenud transformatiivseks tööriistaks andmete hankimisel. Võttes omaks Hangi-renderdamise-käigus mustri ja kasutades Suspense'i toetavaid teeke, saavad arendajad oluliselt parandada oma rakenduste kasutajakogemust, kõrvaldades laadimiskaskaadid, lihtsustades komponendi loogikat ja pakkudes sujuvaid, koordineeritud laadimisolekuid. Koos veapiiridega robustseks veakäsitluseks ja React Server Components'i tulevikulubadusega annab Suspense meile võimu ehitada rakendusi, mis pole mitte ainult jõudluspõhised ja vastupidavad, vaid ka olemuselt meeldivamad kasutajatele üle kogu maailma. Üleminek Suspense'ipõhisele andmete hankimise paradigmale nõuab kontseptuaalset kohandamist, kuid kasu koodi selguse, jõudluse ja kasutajate rahulolu osas on märkimisväärne ja investeeringut väärt.